package net.iceflow.dunsel.client;

import java.util.concurrent.locks.ReentrantLock;
import net.iceflow.dunsel.client.listener.DisconnectionListener;
import net.iceflow.dunsel.client.listener.FailedConnectionListener;
import net.iceflow.dunsel.client.listener.SuccessfulConnectionListener;
import net.iceflow.dunsel.commons.SubscriptionTracker;
import net.iceflow.dunsel.commons.Subscribable;

/**
 * Default implementation of the ClientFacade interface.
 */
class DefaultClientFacadeImpl implements ClientFacade, CommandableFacade {

    /**
     * Command queue.
     */
    private final ClientCommandQueue _commandQueue = new ClientCommandQueue(this);
    /**
     * Lock to prevent dual threads.
     */
    private final ReentrantLock _lock = new ReentrantLock();
    /**
     * Thread to execute the commands.
     */
    private Thread _queueExecutionThread = null;
    /**
     * Tracker for successful connection events.
     */
    private final SubscriptionTracker<SuccessfulConnectionListener, ClientFacade> _successfulConnectionSubscriptions;
    /**
     * Tracker for failed connection events.
     */
    private final SubscriptionTracker<FailedConnectionListener, ClientFacade> _failedConnectionSubscriptions;
    /**
     * Tracker for disconnection events.
     */
    private final SubscriptionTracker<DisconnectionListener, ClientFacade> _disconnectionSubscriptions;

    /**
     * Constructs a new DefaultClientFacadeImpl.
     */
    public DefaultClientFacadeImpl() {
        this._successfulConnectionSubscriptions = new SubscriptionTracker<SuccessfulConnectionListener, ClientFacade>(new SuccessfulConnectionListener.CallAdapter());
        this._disconnectionSubscriptions = new SubscriptionTracker<DisconnectionListener, ClientFacade>(new DisconnectionListener.CallAdapter());
        this._failedConnectionSubscriptions = new SubscriptionTracker<FailedConnectionListener, ClientFacade>(new FailedConnectionListener.CallAdapter());
    }

    /**
     * Attempts to connect to a server.
     */
    @Override
    public void connect(String address, int port) throws IllegalStateException {

        this.injectCommand(CommandType.CONNECT);

        try {
            Thread.sleep(5000);
        } catch (InterruptedException ex) {
            // Intentionally do nothing
        }

        this.notifyDisconnection();
    }

    /**
     * Disconnects from any existing server connection.
     */
    @Override
    public void disconnect() {

        this.injectCommand(CommandType.DISCONNECT);
    }

    @Override
    public void injectCommand(CommandType command) {

        this._commandQueue.addCommand(command);

        if (!this._lock.isLocked()) {
            this._queueExecutionThread = new Thread(new QueueExecutor(this._commandQueue, this._lock));
            this._queueExecutionThread.start();
        }
    }

    @Override
    public void unsubscribeAll() {
        this._successfulConnectionSubscriptions.removeAllListeners();
        this._disconnectionSubscriptions.removeAllListeners();
        this._failedConnectionSubscriptions.removeAllListeners();
    }

    /**
     * @return the successfulConnectionSubscriptions
     */
    @Override
    public Subscribable<SuccessfulConnectionListener> getSuccessfulConnectionSubscriptions() {
        return _successfulConnectionSubscriptions;
    }

    /**
     * @return the disconnectionSubscriptions
     */
    @Override
    public Subscribable<DisconnectionListener> getDisconnectionSubscriptions() {
        return _disconnectionSubscriptions;
    }

    /**
     * @return the failedConnectionSubscriptions
     */
    @Override
    public Subscribable<FailedConnectionListener> getFailedConnectionSubscriptions() {
        return _failedConnectionSubscriptions;
    }

    @Override
    public void notifySuccesfullConnection() {
        this._successfulConnectionSubscriptions.notifyListeners(this);
    }

    @Override
    public void notifyFailedConnection() {
        this._failedConnectionSubscriptions.notifyListeners(this);
    }

    @Override
    public void notifyDisconnection() {
        this._disconnectionSubscriptions.notifyListeners(this);
    }

    private class QueueExecutor implements Runnable {

        private final ClientCommandQueue _commandQueue;
        private final ReentrantLock _lock;

        public QueueExecutor(final ClientCommandQueue commandQueue, final ReentrantLock lock) {
            this._commandQueue = commandQueue;
            this._lock = lock;
        }

        @Override
        public void run() {

            System.out.println("Thread started");

            this._lock.lock();

            try {
                while (this._lock.isLocked() && this._commandQueue.executeNext()) {
                }
            } finally {
                this._lock.unlock();
            }

            System.out.println("Thread ending");
        }
    }
}
